package xhttp

import (
	"context"
	"crypto/tls"
	"net"
	"net/http"
	"strings"

	"gitee.com/lihaiping1603/utils/log"
)

type ServerEngine struct {
	addr    string
	handler http.Handler
	srv     *http.Server
}

func NewServerEngine(handler http.Handler) *ServerEngine {
	return &ServerEngine{handler: handler}
}

func (api *ServerEngine) Start(addr string, tlsCertPath, tlsKeyPath string) error {
	return api.StartWithOption(addr, tlsCertPath, tlsKeyPath)
}

func (api *ServerEngine) StartWithOption(addr string, tlsCertPath, tlsKeyPath string, options ...Option) error {
	var err error = nil
	api.createServer(addr)
	//options
	api.withOptions(options...)
	if strings.HasPrefix(addr, ":") {
		addr = "localhost" + addr
	}
	if tlsCertPath != "" {
		log.Info("xhttp server listen at https://%s", addr)
		// Start the HTTPS server
		if err = api.srv.ListenAndServeTLS(tlsCertPath, tlsKeyPath); err != http.ErrServerClosed {
			log.Error("Error: failed to start server: %v", err)
		}
	} else {
		log.Info("xhttp server listen at http://%s", addr)
		// Start the HTTP server
		if err = api.srv.ListenAndServe(); err != http.ErrServerClosed {
			log.Error("Error: failed to start server: %v", err)
		}
	}
	return err
}

func (api *ServerEngine) StartWithListener(ln net.Listener, tlsCertPath, tlsKeyPath string, options ...Option) error {
	addr, err := getListenerAddr(ln)
	if err != nil {
		return err
	}
	api.createServer(addr)
	//options
	api.withOptions(options...)
	if strings.HasPrefix(addr, ":") {
		addr = "localhost" + addr
	}
	if tlsCertPath != "" {
		log.Info("xhttp server listen at https://%s", addr)
		// Start the HTTPS server
		err = api.srv.ServeTLS(ln, tlsCertPath, tlsKeyPath)
	} else {
		log.Info("xhttp server listen at http://%s", addr)
		// Start the HTTP server
		err = api.srv.Serve(ln)
	}

	if err != http.ErrServerClosed {
		log.Error("Error: failed to start server: %v", err)
	}
	return err
}

func (api *ServerEngine) createServer(addr string) {
	server := &http.Server{
		Addr:    addr,
		Handler: api.handler,
		TLSConfig: &tls.Config{
			MinVersion: tls.VersionTLS13,
		},
	}
	api.addr = addr
	api.srv = server
}

func (api *ServerEngine) Addr() string {
	return api.addr
}

func (api *ServerEngine) withOptions(options ...Option) {
	for _, opt := range options {
		opt.apply(api)
	}
}

//Close会立即关闭所有活动的net.Listeners和状态为statennew、StateActive或StateIdle的任何连接connections。要实现安全和优雅关闭，请使用shutdown。
//Close不会试图关闭(甚至不知道)任何被劫持的连接，比如WebSockets，所以当调用该函数以后，会发现那些被劫持的链接依然是可以继续通讯的。
//Close将返回关闭服务器底层侦听器所返回的任何错误。
func (api *ServerEngine) Stop() {
	if api.srv != nil {
		api.srv.Close()
	}
}

//Shutdown 方法会在不干扰任何活跃连接的情况下关闭服务器。首先，它会关闭所有开着的监听器，然后关闭所有空闲连接，接着无限等待所有连接变成空闲状态，最后关闭。
//如果提供的 context.Context 对象在关闭完成之前过期了，那么，Shutdown 方法返回该 Context 对象的错误信息。否则，它会将正在关闭的服务器的底层监听器的错误返回（如果有的话）。
//一旦调用了 Shutdown 方法，Serve、ListenAndServe 和 ListenAndServeTLS 会立即返回 ErrServerClosed。需要确保程序不退出，而是等待 Shutdown 返回。
//Shutdown不会试图关闭或等待WebSockets等被劫持的连接。如果需要，Shutdown的调用者应该单独通知这些长时间运行的连接关闭，并等待它们关闭。
func (api *ServerEngine) Shutdown(ctx context.Context) error {
	if api.srv != nil {
		return api.srv.Shutdown(ctx)
	}
	return nil
}

func getListenerAddr(ln net.Listener) (string, error) {
	addr := ln.Addr().String()
	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		log.Error("get addr:%s,split host port err:%s", addr, err.Error())
		return addr, err
	}
	if host == "::" {
		host = ""
	}
	addr = host + ":" + port
	return addr, nil
}
